#****************************************#
#          MediaComp for CPython         #
#          v0.1  03/26/10 (Sam)          #
#          v0.2  12/24/10 (David)        #
#          v1.0  05/18/11                #
#          By: Sam Cleveland             #
#              David O'Gwynn             #
#****************************************#

import os,sys,platform
import pprint
import math
import colorsys
import Tkinter
from Tkinter import *
import tkFileDialog
import tkSimpleDialog,tkMessageBox
import tkColorChooser

import Image,ImageTk,ImageDraw

def getPath(filename):
    if os.path.exists(filename):
        return filename
    return os.path.join(Media.path,filename)

def defaultRC():
    path = "Y:\\cs106\\MediaSources\\"
    if platform.platform()[:6].lower()=='darwin':
        path = os.path.join(os.environ['HOME'],'MediaSources')
    rcdict = {
        'path': path,
        }
    return rcdict
def getMediaRCPath():
    if sys.platform.startswith('win'):
        home = os.path.abspath(os.environ['USERPROFILE'])
    else:
        home = os.path.abspath(os.environ['HOME'])
    rcpath = os.path.join(home,'.idlemedia')
    return rcpath
def getMediaRC():
    rcpath = getMediaRCPath()
    if not os.path.exists(rcpath):
        f = open(rcpath,'w')
        f.write(pprint.pformat(defaultRC()))
        f.close()
    f = open(rcpath)
    rcdict = eval(f.read())
    f.close()
    return rcdict
def setMediaRC(rcdict):
    rcpath = getMediaRCPath()
    f = open(rcpath,'w')
    f.write(pprint.pformat(rcdict))
    f.close()
    

class Media:
    #default media path is current working directory
    rcdict = getMediaRC()
    path = rcdict['path']

    menudefs=[('media',[('Placeholder', '<<media>>')])]

    editwin = None
    text = None
    root = None

    def __init__(self,editwin):
        Media.editwin=editwin
        Media.text=editwin.text
        Media.root = editwin.root
        #print editwin
            
    def media_event(self, event=None):
        filetypes = [
            ("All HTML files", "*.htm *.html", "TEXT"),
            ("All files", "*"),
            ]
        top = Tkinter.Toplevel(self.editwin.root)
        return 'break'

        


##################################################
#                                                #
#              Files Functions                   #
#     - setMediaPath([path])                     #
#     - setMediaFolder([folder])                 #
#     - getMediaPath([filename])                 #
#     - pickAFile()                              #
#     - pickAFolder()                            #
#     - * getShortPath(path)                     #
#     - setLibPath([directory])                  #
#                                                #
##################################################

files_functions = [
    'setMediaPath',
    'setMediaFolder',
    'getMediaPath',
    'pickAFile',
    'pickAFolder',
    'getShortPath',
    'setLibPath',
]


#set a new media path
def setMediaFolder(folder=''):
    '''
    Sets the global media path to the provided path.
    If no path is given then a file picker window will
    open and the selected folder will be used.
    '''
    setMediaPath(folder)
   
def setMediaPath(folder=''):
    '''
    Sets the global media path to the provided folder.
    If no folder is given then a file picker window will
    open and the selected folder will be used.
    '''
    filetypes = [
        ("Python and text files", "*.py", "TEXT"),
        ("All text files", "*", "TEXT"),
        ("All files", "*"),
        ]

    if(not folder):
        directory=tkFileDialog.Directory(master=Media.text).show()
        if directory:
            Media.path = directory
            rcdict = getMediaRC()
            rcdict['path'] = directory
            setMediaRC(rcdict)
            print 'Media path set to:',directory
        else:
            print 'Media path not changed. Current path:',Media.path
    else:
        if(os.path.isdir(folder)):
            Media.path = folder
            rcdict = getMediaRC()
            rcdict['path'] = directory
            setMediaRC(rcdict)
            print "Media folder set to: " + Media.path
        else:
            print "Folder doesn't exist! Path not set. Current path:",Media.path


def getMediaPath(filename=''):
    '''
    Takes an optional filename and returns the current
    media path + the filename or if no filename is given
    then just the current media path is returned.
    '''
    return os.path.join(Media.path, filename)

def getMediaFolder(filename=''):
    '''
    Takes an optional filename and returns the current
    media path + the filename or if no filename is given
    then just the current media path is returned.
    '''
    getMediaPath(filename)

def pickAFile():
    '''
    Opens a file picker and returns the selected file.
    '''
    filetypes = [
        ("Python and text files", "*.py", "TEXT"),
        ("All text files", "*", "TEXT"),
        ("All files", "*"),
        ]
    filename = tkFileDialog.SaveAs(master=Media.text,filetypes=filetypes).show()
    return filename

def pickAFolder():
    '''
    Opens a folder picker and returns the path to the selected folder.
    '''
    directory=tkFileDialog.Directory(master=Media.text).show()
    return directory

def getShortPath(path):
    '''
    Returns the short non-absolute version of the given path.
    '''
    raise NotImplementedError

def setLibPath(directory=''):
    '''
    Adds a directory to look in for modules you want to import.
    The directory parameter is optional. If it it left out a file
    picker will open.
    '''
    raise NotImplementedError

####################################################
#                                                  #
#           Input/Output Functions                 #
#                                                  #
#           - requestNumber()                      #
#           - requestInteger()                     #
#           - requestIntegerInRange()              #
#           - requestString()                      #
#           - showWarning()                        #
#           - showInformation()                    #
#           - showError()                          #
#           - printNow()                           #
#                                                  #
####################################################

io_functions = [
    'requestNumber',
    'requestInteger',
    'requestIntegerInRange',
    'requestString',
    'showWarning',
    'showInformation',
    'showError',
    'printNow',
    'explore',
]

def requestNumber(message):
    '''
    Prompt the user for a decimal number.
    The dialog will keep appearing until a valid number is entered.
    '''
    #add loop to validate input
    #loop until correct
    num = tkSimpleDialog.askfloat('Need a floating point number...',
                                  message,
                                  parent=Media.root)
    return num

def requestInteger(message):
    '''
    Prompt the user for an integer.
    The dialog will keep appearing until a valid integer is entered.
    '''
    #add loop to validate input
    #loop until correct
    num = tkSimpleDialog.askinteger('Need an integer...',
                                    message,
                                    parent=Media.root)
    return num

def requestIntegerInRange(message, minInt, maxInt):
    '''
    Prompt user for an integer between minInt and maxInt.
    The dialog will keep appearing until a valid integer
    within the range is entered.
    '''
    i = requestInteger(message)
    while( i < minInt or i > maxInt):
        print 'Integer outside of range ({0}-{1})'.format(minInt, maxInt)
        i = requestInteger(message+' ')
    return i

def requestString(message):
    '''
    Prompt user for a string.
    '''
    s = tkSimpleDialog.askstring('Need a string...',
                                 message,
                                 parent=Media.root)
    return s

def showWarning(message):
    '''
    Shows a warning dialog to the user with the given message.
    '''
    tkMessageBox.showwarning('Warning!',message,master=Media.root)

def showInformation(message):
    '''
    Shows an information dialog to the user with the given message.
    '''
    tkMessageBox.showinfo('For your information...',message,master=Media.root)

def showError(message):
    '''
    Shows an error dialog to the user with the given message.
    '''
    tkMessageBox.showerror('ERROR!',message,master=Media.root)

def printNow(output):
    '''
    Prints the specified output without waiting for buffering.
    '''
    print output

def explore(someMedia):
    PictureTool(someMedia)


#########################################
#                                       #
#       Color class and functions       #
#                                       #
#########################################

color_functions = [
    'distance',
    'makeColor',
    'makeDarker',
    'makeLighter',
    'pickAColor',
    'getColorWrapAround',
    'setColorWrapAround',
    ]

def distance(color1, color2):
    ''' Takes two color objects and returns a single number
    representing the distance between the colors. The red, green, and
    blue values of the colors are taken as a point in (x, y, z) space,
    and the Cartesian distance is computed.
    '''
    return color1.distance(color2)

def makeColor(red=0, green=0, blue=0):
    '''Takes three integer inputs for the red, green, and blue
    components and returns a color object.  If only a red value is
    given it is used as the intensity of a gray color. Also it works
    with only a color as input and returns a new color object with the
    same RGB values as the original.'''
    return Color(red, green, blue)

def makeDarker(color):
    ''' Takes a color and returns a slightly darker version of the
    original color.
    '''
    return color.makeDarker()

def makeLighter(color):
    ''' Takes a color and returns a slightly ligher version of the
    original color.
    '''
    return color.makeLighter()

def pickAColor():
    ''' Opens a color chooser to let the user pick a color and returns
    it. Takes no input.
    '''
    color = tkColorChooser.askcolor(master=Media.root)
    r,g,b = color[0]
    return makeColor(r,g,b)

def getColorWrapAround():
    '''
    Takes no input, and returns the current value of ColorWrapAround
    (0 or 1). If it is true, color values will wrap-around (356 mod
    256 = 100); if false, color values lower than 0 will be forced to
    0 and higher than 255 forced to 255.  Default is false.
    '''
    raise NotImplementedError

def setColorWrapAround(flag):
    '''
    If the flag is set to true, color values will wrap around (mod
    256); if false, colors > 255 will be set to 255, colors < 0 will
    be set to 0.
    '''
    raise NotImplementedError


class Color:
    def __init__(self, red=0, green=0, blue=0):
        self.rgb = red,green,blue

    def __getitem__(self,index):
        return self.rgb[index]
    def __len__(self):
        return 3

    def __repr__(self):
        r,g,b = self.rgb
        return '<Color R=%s G=%s B=%s at %x>'%(r,g,b,id(self))

    def tuple(self):
        return tuple(self.rgb)

    def distance(self, color2):
        '''
        Takes two color objects and returns a single number
        representing the distance between the colors. The red, green,
        and blue values of the colors are taken as a point in (x, y,
        z) space, and the Cartesian distance is computed.
        '''
        r0,g0,b0 = self.rgb
        r1,g1,b1 = color2.rgb
        dr,dg,db = r0-r1,g0-g1,b0-b1
        return math.sqrt(dr*dr+dg*dg+db*db)

    def makeDarker(self):
        '''
        Takes a color and returns a slightly darker version of the
        original color.
        '''
        return self.makeAdjusted(0.9)
    
    def makeLighter(self):
        '''
        Takes a color and returns a slightly ligher version of the
        original color.
        '''
        return self.makeAdjusted(1.1)

    def makeAdjusted(self,factor):
        r,g,b = self.rgb
        r,g,b = r/255.,g/255.,b/255.
        hls = list(colorsys.rgb_to_hls(r,g,b))
        hls[-2] = hls[-2]*factor
        r,g,b = map(int,colorsys.hls_to_rgb(r,g,b))
        return Color(r,g,b)

    def getColorWrapAround(self):
        '''
        Takes no input, and returns the current value of
        ColorWrapAround (0 or 1). If it is true, color values will
        wrap-around (356 mod 256 = 100); if false, color values lower
        than 0 will be forced to 0 and higher than 255 forced to 255.
        Default is false.
        '''
        raise NotImplementedError

    def setColorWrapAround(self, flag):
        '''
        If the flag is set to true, color values will wrap around (mod
        256); if false, colors > 255 will be set to 255, colors < 0
        will be set to 0.
        '''
        raise NotImplementedError

##########################################
#                                        #
#      Pixel class and Functions         #
#                                        #
##########################################
pixel_functions = [
    'getColor',
    'setColor',
    'getRed',
    'getGreen',
    'getBlue',
    'setRed',
    'setGreen',
    'setBlue',
    'getX',
    'getY',
    ]

def getColor(pixel):
    '''
    Takes a pixel and returns the Color object of that pixel.
    '''
    return pixel.getColor()

def setColor(pixel, color):
    '''
    Sets the pixel to the given color.
    '''
    return pixel.setColor(color)

def getRed(pixel):
    ''' Returns the red value (between 0 and 255) of the pixel.
    '''
    return pixel.getRed()

def getGreen(pixel):
    ''' Returns the green value (between 0 and 255) of the pixel.
    '''
    return pixel.getGreen()

def getBlue(pixel):
    ''' Returns the blue value (between 0 and 255) of the pixel.
    '''
    return pixel.getBlue()

def setRed(pixel, redValue):
    ''' Sets the red value for the pixel.
    '''
    pixel.setRed(redValue)

def setGreen(pixel, greenValue):
    ''' Sets the green value for the pixel.
    '''
    pixel.setGreen(greenValue)

def setBlue(pixel, blueValue):
    ''' Sets the blue value for the pixel.
    '''
    pixel.setBlue(blueValue)

def getX(pixel):
    ''' Returns the x coordinate of the pixel.
    '''
    return pixel.getX()

def getY(pixel):
    ''' Returns the y coordinate of the pixel.
    '''
    return pixel.getY()


class Pixel:
    def __init__(self,picture,x,y):
        self.picture = picture
        self.pos = x,y
    def __len__(self):
        return 3
    def __getitem__(self,index):
        return self.picture.data[self.pos][index]
    def __setitem__(self,index,value):
        color = self.picture.data[self.pos]
        color[index] = value
        self.picture[self.pos] = color
    def __repr__(self):
        repr = '<Pixel (%s, %s, %s) at (%s, %s)>'
        r,g,b = self[0],self[1],self[2]
        x,y = self.pos
        repr = repr % tuple([str(v) for v in [r,g,b,x,y]])
        return repr

    def _index(self,index):
        cmap = {'red': 0, 'green':1, 'blue':2}
        index = cmap.get(index,index)
        if type(index) is not int:
            raise IndexError, ('%s is not a valid index'
                               ' for pixel objects'%index)
        elif index > 2:
            raise IndexError, ('%s is an incorrect index:'
                               ' 0, 1 and 2 are valid'%index)
        return index
    def _value(self,value):
        if type(value) not in (int,float):
            raise AttributeError, ("Pixel color values must be"
                                   " integers in the range 0 ... 255")
        elif (value < 0) or (value > 255):
            raise AttributeError, ("Pixel color values must be"
                                   " integers in the range 0 ... 255")
        value = int(value)
        return value

    def __getitem__(self,index):
        index = self._index(index)
        return self.picture.data[self.pos][index]
    def __setitem__(self,index,value):
        index = self._index(index)
        value = self._value(value)
        fmap = {0:self.setRed,1:self.setGreen,2:self.setBlue}
        fmap[index](value)
        
    def getColor(self):
        '''
        Color object of this pixel's color values.
        '''
        x,y = self.pos
        p = self.picture.data[x,y]
        return Color(*p)

    def setColor(self, color):
        ''' Sets the pixel to the given color.
        '''
        x,y = self.pos
        if hasattr(color,'rgb'):
            color = color.rgb
        else:
            color = tuple(color)
        self.picture.data[x,y] = color

    def getRed(self):
        ''' Returns the value (between 0 and 255) of the amount of red
        in the given pixel.
        '''
        x,y = self.pos
        r,g,b = self.picture.data[x,y]
        return r

    def getGreen(self):
        ''' Returns the value (between 0 and 255) of the amount of
        green in the given pixel.
        '''
        x,y = self.pos
        r,g,b = self.picture.data[x,y]
        return g

    def getBlue(self):
        ''' Returns the value (between 0 and 255) of the amount of
        blue in the given pixel.
        '''
        x,y = self.pos
        r,g,b = self.picture.data[x,y]
        return b

    def setRed(self, redValue):
        '''
        Takes a pixel object and a red value between 0 and 255. Sets
        the amount of red for that pixel to the given value.
        '''
        redValue = self._value(redValue)
        x,y = self.pos
        r,g,b = self.picture.data[x,y]
        self.picture.data[x,y] = redValue,g,b

    def setGreen(self, greenValue):
        '''
        Takes a pixel object and a green value between 0 and 255. Sets
        the amount of green for that pixel to the given value.
        '''
        greenValue = self._value(greenValue)
        x,y = self.pos
        r,g,b = self.picture.data[x,y]
        self.picture.data[x,y] = r,greenValue,b

    def setBlue(self, blueValue):
        '''
        Takes a pixel object and a blue value between 0 and 255. Sets
        the amount of blue for that pixel to the given value.
        '''
        blueValue = self._value(blueValue)
        x,y = self.pos
        r,g,b = self.picture.data[x,y]
        self.picture.data[x,y] = r,g,blueValue

    def getX(self):
        '''
        Takes a pixel object and returns the x position of that pixel
        in a picture.
        '''
        return self.pos[0]

    def getY(self):
        '''
        Takes a picel object and returns the y position of that pixel
        in a picture.
        '''
        return self.pos[1]

####################################################
#                                                  #
#         Pictures Class and Functions             #
#                                                  #
#         - addArc()                               #
#         - addArcFilled()                         #
#         - addLine()                              #
#         - addOval()                              #
#         - addOvalFilled()                        #
#         - addRect()                              #
#         - addRectFilled()                        #
#         - addText()                              #
#         - addTextWithStyle()                     #
#         - copyInto()                             #
#         - duplicatePicture()                     #
#         - getHeight()                            #
#         - getWidth()                             #
#         - getPixel()                             #
#         - getPixels()                            #
#         - * getPixelAt()                         #
#         - makePicture()                          #
#         - makeEmptyPicture()                     #
#         - makeStyle()                            #
#         - show()                                 #
#         - repaint()                              #
#         - writePictureTo()                       #
#         - openPictureTool()                      #
#         - setAllPixelsToAColor()                 #
#         - explore()                              #
#                                                  #
####################################################

picture_functions = [
    'addArc',
    'addArcFilled',
    'addLine',
    'addOval',
    'addOvalFilled',
    'addRect',
    'addRectFilled',
    'addText',
    'addTextWithStyle',
    'copyInto',
    'duplicatePicture',
    'getHeight',
    'getWidth',
    'getPixel',
    'getPixels',
    'getPixelAt',
    'makePicture',
    'makeEmptyPicture',
    'makeStyle',
    'show',
    'repaint',
    'writePictureTo',
    'openPictureTool',
    'setAllPixelsToAColor',
]

def copyInto(picture, bigPicture, startX, startY):
    '''
    Takes two pictures, and a x and y position as input. Modifies
    bigPicture by coping into it as much of picture as will fit,
    starting at the x, y position in the destination picture.
    '''
    picture.copyInto(bigPicture, startX, startY)

def duplicatePicture(picture):
    '''
    Takes a picture as input and returns a new picture object with the
    same image as the original.
    '''
    return picture.duplicatePicture()

def getHeight(picture):
    '''
    Takes a picture as input and returns its height in pixels.
    '''
    return picture.getHeight()

def getWidth(picture):
    '''
    Takes a picture as input and returns its width in pixels.
    '''
    return picture.getWidth()

def getPixel(picture, x, y):
    '''
    Takes a picture, a x and y position and returns the Pixel object
    at the given point in the picture. (same as getPixelAt)
    '''
    return picture.getPixel(x,y)

getPixelAt=getPixel

def getPixels(picture):
    '''
    Takes a picture as input and returns the sequence of Pixel objects
    in the picture.
    '''
    return picture.getPixels()

def makePicture(filename):
    '''
    Takes a filename or complete path to a file and returns a picture
    object created from the given file.
    '''
    if not os.path.exists(filename):
        # see if picture exists in media path
        filename = os.path.join(Media.path,filename)
        if not os.path.exists(filename):
            raise IOError,'The file path you gave ('+filename+") doesn't exist!"
    p = Picture()
    p.fromFile(filename)
    return p

def makeEmptyPicture(width, height, color=Color(255,255,255)):
    '''
    Makes a new "empty" picture and returns it to you. The width and
    height must be between 0 and 10000. Default color is white.
    '''
    image = Image.new('RGB',(width,height),tuple(color))
    picture = Picture()
    picture.fromImage(image)
    return picture
    

def makeStyle(fontName, emphasis, size):
    '''
    Takes a font name, emphasis, and size in points as input. Returns
    a Font object with the given parameters.
    '''
    raise NotImplementedError

def show(picture):
    '''
    Shows the picture provided as input.
    '''
    picture.show()

def repaint(picture):
    '''
    Repaints the picture if it has been opened in a window from
    show(picture), otherwise a new window will be opened.
    '''
    picture.repaint()

def writePictureTo(picture, path):
    '''
    Takes a picture and a filename as input, then writes the picture
    to the file as JPEG.  PNG, or BMP.(File type is decided by the
    filename extension.
    '''
    picture.write(path)

def addArc(picture, startX, startY,
           width, height, start,
           angle, color=Color(0,0,0)):
    ''' 
    Adds an outline of an arc starting at (x, y) at an initial angle
    of "start" with the given width and height. The angle of the arc
    itself is "angle" which is relative to "start". Default color is
    black.
    '''
    picture.addArc(startX, startY, width,
                   height, start, angle,
                   color)

def addArcFilled(picture, startX, startY,
                 width, height, start,
                 angle, color=Color(0,0,0)):
    '''
    Adds a filled arc starting at (x, y) at an initial angle of
    "start" with the given width and height. The angle of the arc
    itself is "angle" which is relative to "start". Default color is
    blank.
    '''
    picture.addArcFilled(startX, startY, width,
                         height, start, angle,
                         color)

def addLine(picture, startX, startY,
            endX, endY, color=Color(0,0,0)):
    '''
    Takes a picture, starting x and y position, and an ending x and y)
    positions, and (optionally) a color as input. Adds a line from the
    starting point to the ending point in the picture. Default color
    is black.
    '''
    picture.addLine(startX, startY, endX,
                    endY, color)

def addOval(picture, startX, startY,
            width, height, color=Color(0,0,0)):
    '''
    Takes a picture, starting x and y position, a width and height,
    and (optionally) a color as input.  Adds an oval outline to the
    given deminsions using the x and y as the upper left corner of the
    bounding rectangle. Default color is black.
    '''
    picture.addOval(startX, startY, width,
                    height, color)

def addOvalFilled(picture, startX, startY,
                  width, height, color=Color(0,0,0)):
    '''
    Takes a picture, starting x and y position, a width and height,
    and (optionally) a color as input.  Adds filled oval of the given
    deminsions using the x and y as the upper left corner of the
    bounding rectangle. Default color is black.
    '''
    picture.addOvalFilled(startX, startY, width,
                          height, color)

def addRect(picture, startX, startY, width,
            height, color=Color(0,0,0)):
    '''
    Takes a picture, starting x and y position, a width and height,
    and (optionally) a color as input.  Adds a rectangle outline of
    the given deminsions using the x and y as the upper left
    corner. Default color is black.
    '''
    picture.addRect(startX, startY, width,
                    height, color)

def addRectFilled(picture, startX, startY,
                  width, height, color=Color(0,0,0)):
    '''
    Takes a picture, a starting x and y position, a width and height,
    and (optionally) a color as input.  Adds a filled rectangle of the
    given dimensions using the x and y as the upper left
    corner. Default color is black.
    '''
    picture.addRectFilled(startX, startY, width,
                          height, color)

def addText(picture, x, y,
            text, color=Color(0,0,0)):
    '''
    Takes a picture, a x and y position, a string of text to be
    written to the picture, and (optionally) a color as input.
    Default color is black.
    '''
    picture.addText(x, y, text, color)

def addTextWithStyle(picture, x, y,
                     text, style, color=Color(0,0,0)):
    '''
    Takes a picture, a x and y position, a string of text to be
    written to the picture, a font style and (optionally) a color as
    input. Default color is black.
    '''
    picture.addTextWithStyle(x, y, text,
                             style, color)

def openPictureTool(picture):
    '''
    Opens the Picture Tool explorer, which lets you examine the pixels
    of an image.
    '''
    picture.openPictureTool()

def setAllPixelsToAColor(picture, color):
    '''
    Modifies the picture so that every pixel is the given color.
    '''
    picture.setAllPixelsToAColor()



###################################
#   Picture Class and Functions   #
###################################

class PixelList(object):
    def __init__(self,picture):
        self.picture = picture
    def __repr__(self):
        repr = '<PixelList [%s, %s, ..., %s, %s]>'
        pixels = [list(self[0]),list(self[1]),list(self[-2]),list(self[-1])]
        repr = repr % (tuple([str(p) for p in pixels]))
        return repr
    def __len__(self):
        w,h = self.picture.getSize()
        return w*h
    def __getitem__(self,index):
        w,h = self.picture.getSize()
        if type(index) is not int:
            raise IndexError, 'index must be of type int'
        if index < 0:
            index = w*h + index
        if (index < 0) or (index >= w*h):
            raise IndexError, 'index %s out of range'%index
        
        x,y = index%w,index/w
        return Pixel(self.picture,x,y)

class Picture(object):
    
    def __init__(self):
        self.path = None
        self.image = None
        self.data = None
        self.draw = None
        self.pixels = None

    def __repr__(self):
        clstr = str(self.__class__)
        w,h=self.getSize()
        repr = "<%s instance \n  path: '%s' \n  width: %s, height: %s>"
        repr = repr % (clstr,self.path,w,h)
        return repr

    def __checkXY(self,xy):
        w,h = self.getSize()
        if type(xy) is int:
            i=xy
            xy = i%w,i/w
            return xy
        elif type(xy) in [list,tuple]:
            return tuple(xy)
        elif type(xy) is slice:
            return xy
        else:
            err = ('When assigning a color value to a pixel location,'
                   ' the index must be given either as an integer'
                   ' (i.e. picture[4]),'
                   ' a slice (i.e. picture[5:10]), or as an (x,y)'
                   ' tuple/list coordinate location (i.e. picture[10,10]).')
            raise IndexError,err
    def __len__(self):
        w,h = self.getSize()
        return w*h
    def __getitem__(self,xy):
        xy = self.__checkXY(xy)
        if type(xy) is slice:
            indices = xy.indices(len(self))
            pixels = [self.data[self.__checkXY(i)] for i in range(*indices)]
            return pixels
        else:
            return self.data[xy]
    def __setitem__(self,xy,rgb):
        xy = self.__checkXY(xy)

        if type(xy) is slice:
            raise IndexError,'Slices are not allowed when setting pixel data'
        # check color
        rgb = tuple(rgb)
        if type(rgb) not in [list,tuple]:
            err = ('When assigning a color value to a pixel location,'
                   ' the color must be given either as a Color object'
                   ' or as a tuple/list of RGB values.')
            raise TypeError,err
        self.data[xy] = tuple(rgb)

    def init(self):
        self.data = self.image.load()
        self.draw = ImageDraw.Draw(self.image)
        self.pixels = PixelList(self)

    def fromFile(self,path):
        self.path = path
        self.image = Image.open(path).convert('RGB')
        self.init()

    def fromPicture(self,picture):
        self.path = picture.path
        self.image = picture.image.copy()
        self.init()

    def fromImage(self,image):
        self.path = None
        self.image = image
        self.init()

    def copyInto(self, bigPicture, startX, startY):
        '''
        Takes two pictures, and a x and y position as input. Modifies
        bigPicture by coping into it as much of picture as will fit,
        starting at the x, y position in the destination picture.
        '''
        raise NotImplementedError

    def duplicatePicture(self):
        '''
        Takes a picture as input and returns a new picture object with
        the same image as the original.
        '''
        p = Picture()
        p.fromPicture(self)
        return p

    def getSize(self):
        return self.image.size

    def getHeight(self):
        '''
        Takes a picture as input and returns its height in pixels.
        '''
        w,h = self.getSize()
        return h

    def getWidth(self):
        '''
        Takes a picture as input and returns its width in pixels.
        '''
        w,h = self.getSize()
        return w

    def getPixel(self, x, y):
        '''
        Takes a picture, a x and y position and returns the Pixel
        object at the given point in the picture. (same as getPixelAt)
        '''
        w,h = self.getSize()
        errText = 'BAD INDEX! \n'
        if x<0:
            errText += '-- x must be greater than zero. \n'
        elif x>=w:
            errText += ('-- x must be less than the width of'+
                        ' this picture (width=%s). \n'%w)
        if y<0:
            errText += '-- y must be greater than zero. '
        elif y>=h:
            errText += ('-- y must be less than the height of'+
                        ' this picture (heigh=%s). '%h)
        if len(errText)>12:
            raise IndexError,errText

        return Pixel(self,x,y)

    def getPixels(self):
        '''
        Takes a picture as input and returns the sequence of Pixel
        objects in the picture.
        '''
        return self.pixels
        p = Pixel(self,-1,-1)
        def pixels(image=self.image,data=self.data,pixel=p):
            i=0
            w,h = image.size
            while i<w*h:
                x,y = i%w,i/w
                pixel.pos = x,y
                yield pixel
                i+=1
            
        return pixels()

    top = None
    label = None
    labelphoto = None
    def _catch_delete(self):
        #print 'deleting "show" window',self.top
        try:
            self.top.destroy()
        except:
            pass
    def _catch_destroy(self,event):
        self.top = None
        self.label = None
        self.labelphoto = None
    def _catch_escape(self,event):
        try:
            self.top.destroy()
        except:
            pass
    def show(self):
        '''
        Shows the picture provided as input.
        '''
        root = Media.root
        if self.top is None:
            self.top = top = Tkinter.Toplevel(root)
            top.protocol('WM_DELETE_WINDOW',self._catch_delete)
            top.bind('<Destroy>',self._catch_destroy)
            top.bind('<Escape>',self._catch_escape)
            photo = self.tk()
            self.label = Tkinter.Label(top,image=photo)
            self.labelphoto = photo
            self.label.pack()
        else:
            photo = self.tk()
            self.labelphoto = photo
            self.label['image'] = photo
        self.label.update()
        
    def tk(self):
        return ImageTk.PhotoImage(self.image,master=Media.root)

    def repaint(self):
        '''
        Repaints the picture if it has been opened in a window from
        show(picture), otherwise a new window will be opened.
        '''
        self.show()

    def write(self, path):
        '''
        Takes a picture and a filename as input, then writes the
        picture to the file as JPEG.  PNG, or BMP.(File type is
        decided by the filename extension.
        '''
        if type(path) is not str:
            return
        mpath = getPath(path)
        self.image.save(mpath)

    def addArc(self, startX, startY,
               width, height, start,
               angle, color=(0,0,0)):
        '''
        Adds an outline of an arc starting at (x, y) at an initial
        angle of "start" with the given width and height. The angle of
        the arc itself is "angle" which is relative to
        "start". Default color is black.
        '''
        self.draw.arc((startX,startY,startX+width,startY+height),
                      start,start+angle,tuple(color))

    def addArcFilled(self, startX, startY,
                     width, height, start,
                     angle, color=(0,0,0)):
        '''
        Adds a filled arc starting at (x, y) at an initial angle of
        "start" with the given width and height. The angle of the arc
        itself is "angle" which is relative to "start". Default color
        is blank.
        '''
        self.draw.pieslice((startX,startY,startX+width,startY+height),
                           start,start+angle,fill=tuple(color))
    
    def addLine(self, startX, startY,
                endX, endY, color=(0,0,0)):
        ''' Takes a starting x and y position, and an ending x and y)
        positions, and (optionally) a color as input. Adds a line from
        the starting point to the ending point in the picture. Default
        color is black.
        '''
        self.draw.line(((startX,startY),(endX,endY)),fill=tuple(color))

    def addOval(self, startX, startY,
                width, height, color=(0,0,0)):
        '''
        Takes a picture, starting x and y position, a width and
        height, and (optionally) a color as input.  Adds an oval
        outline to the given deminsions using the x and y as the upper
        left corner of the bounding rectangle. Default color is black.
        '''
        self.draw.ellipse((startX,startY,startX+width,startY+height),
                          outline=tuple(color))

    def addOvalFilled(self, startX, startY,
                      width, height, color=(0,0,0)):
        '''
        Takes a picture, starting x and y position, a width and
        height, and (optionally) a color as input.  Adds filled oval
        of the given deminsions using the x and y as the upper left
        corner of the bounding rectangle. Default color is black.
        '''
        self.draw.ellipse((startX,startY,startX+width,startY+height),
                          fill=tuple(color))

    def addRect(self, startX, startY, width,
                height, color=(0,0,0)):
        '''
        Takes a picture, starting x and y position, a width and
        height, and (optionally) a color as input.  Adds a rectangle
        outline of the given deminsions using the x and y as the upper
        left corner. Default color is black.
        '''
        self.draw.rectangle((startX,startY,startX+width,startY+height),
                            outline=tuple(color))

    def addRectFilled(self, startX, startY,
                      width, height, color=(0,0,0)):
        '''
        Takes a picture, a starting x and y position, a width and
        height, and (optionally) a color as input.  Adds a filled
        rectangle of the given dimensions using the x and y as the
        upper left corner. Default color is black.
        '''
        self.draw.rectangle((startX,startY,startX+width,startY+height),
                            fill=tuple(color))

    def addText(self, x, y,
                text, color=(0,0,0)):
        '''
        Takes a picture, a x and y position, a string of text to be
        written to the picture, and (optionally) a color as input.
        Default color is black.
        '''
        self.draw.text((x,y),text,fill=tuple(color))

    def addTextWithStyle(self, x, y,
                         text, style, color=(0,0,0)):
        '''
        Takes a picture, a x and y position, a string of text to be
        written to the picture, a font style and (optionally) a color
        as input. Default color is black.
        '''
        raise NotImplementedError

    def openPictureTool(self):
        '''
        Opens the Picture Tool explorer, which lets you examine the
        pixels of an image.
        '''
        tool = PictureTool([self])

    def setAllPixelsToAColor(self, color):
        '''
        Modifies the picture so that every pixel is the given color.
        '''
        w,h=self.getSize()
        self.draw.rectangle((0,0,w,h),fill=tuple(color),
                            outline=tuple(color))

class PictureTool(Tkinter.Toplevel):
    def _catch_delete(self):
        #print 'deleting "show" window',self.top
        try:
            self.destroy()
        except:
            pass
    def _catch_destroy(self,event):
        self.frames = None
        self.tkframes = None
    def __init__(self,frames):
        Tkinter.Toplevel.__init__(self,master=Media.root)
        self.protocol('WM_DELETE_WINDOW',self._catch_delete)
        self.bind('<Destroy>',self._catch_destroy)
        self.bind('<Escape>',self.escape)

        if type(frames) not in (list,tuple):
            frames = [frames]
        self.frames = frames[:]
        self.tkframes = [f.tk() for f in frames]
        self.curFrame = 0

        self.xyFrame = Frame(self)

        xlabel = Label(self.xyFrame,text='X:')
        self.xvar = StringVar(master=Media.root)
        self.xentry = Entry(self.xyFrame,textvariable=self.xvar)
        self.xentry.bind('<Return>',self.xyReturn)
        self.xvar.set('0')
        xlabel.pack(side='left')
        self.xentry.pack(side='left')

        ylabel = Label(self.xyFrame,text='Y:')
        self.yvar = StringVar(master=Media.root)
        self.yentry = Entry(self.xyFrame,textvariable=self.yvar)
        self.yentry.bind('<Return>',self.xyReturn)
        self.yvar.set('0')
        ylabel.pack(side='left')
        self.yentry.pack(side='left')

        refreshB = self.refreshB = Button(self.xyFrame,text='Refresh',
                                      command=self.refreshPicture)
        refreshB.pack(side='left')

        self.xyFrame.pack(side='top')
        
        self.rgbFrame = Frame(self)
        rlabel = Label(self.rgbFrame,text='R:')
        rlabel.pack(side='left')
        self.rvar = StringVar(master=Media.root)
        self.rvar.set('0')
        rval = Label(self.rgbFrame,textvariable=self.rvar)
        rval.pack(side='left')

        glabel = Label(self.rgbFrame,text='G:')
        glabel.pack(side='left')
        self.gvar = StringVar(master=Media.root)
        self.gvar.set('0')
        gval = Label(self.rgbFrame,textvariable=self.gvar)
        gval.pack(side='left')

        blabel = Label(self.rgbFrame,text='B:')
        blabel.pack(side='left')
        self.bvar = StringVar(master=Media.root)
        self.bvar.set('0')
        bval = Label(self.rgbFrame,textvariable=self.bvar)
        bval.pack(side='left')

        clabel = Label(self.rgbFrame,text='Color at location:')
        clabel.pack(side='left')
        color00 = self.frames[0].data[0,0]
        self.colorFrame = Frame(self.rgbFrame,width=20,height=20,
                                bg=self.rgbToHtml(color00))
        self.colorFrame.pack(side='left')

        self.rgbFrame.pack(side='top')

        # frame selection
        fs = Frame(self)
        flabel = Label(fs,text="Frame:")
        self.fvar = StringVar(master=Media.root)
        self.fentry = Entry(fs,textvariable=self.fvar,width=5)
        self.fentry.bind('<Return>',self.fentryReturn)
        self.fvar.set('0')
        flabel.pack(side='left')
        self.fentry.pack(side='left')
        self.fsHeight = 20
        self.frameSelector = Canvas(fs,height=self.fsHeight,bd=1,
                                    bg='#ccc',relief='sunken')
        self.frameSelector.bind('<Button-1>',self.fsButton1)
        self.frameSelector.bind('<B1-Motion>',self.fsButton1Motion)
        self.frameSelector.pack(side='left',fill='x',expand=1)
        fs.pack(side='top',fill='x',expand=1)

        w,h = self.frames[0].getSize()
        self.canvas = Canvas(self,width=w,height=h,bd=1,relief='solid')
        self.canvas.bind('<Button-1>',self.button1)
        self.canvas.bind('<B1-Motion>',self.button1motion)
        self.canvas.pack(side='top')
        self.padx = 4
        self.pady = 4

        tkframe = self.tkframes[self.curFrame]
        self.imageTag = self.canvas.create_image(self.padx,self.pady,
                                                 image=tkframe,anchor='nw')

        self.pos = (0,0)
        self.cursorSize = cs = 6
        self.cursorWindow = cw = 2
        x,y = self.pos[0]+self.padx,self.pos[1]+self.pady
        self.cursorTags = [self.canvas.create_line(x-cs,y-cs,x-cw,y-cw,width=2),
                           self.canvas.create_line(x-cs,y+cs,x-cw,y+cw,width=2),
                           self.canvas.create_line(x+cs,y-cs,x+cw,y-cw,width=2),
                           self.canvas.create_line(x+cs,y+cs,x+cw,y+cw,width=2),
                           ]

    def refreshPicture(self):
        self.tkframes = [f.tk() for f in self.frames]
        tkframe = self.tkframes[self.curFrame]
        self.canvas.itemconfigure(self.imageTag,image=tkframe)

    def rgbToHtml(self,rgb):
        return '#%02x%02x%02x'%rgb

    def xyReturn(self,event):
        xs,ys = self.xvar.get(),self.yvar.get()
        if (not xs.isdigit()) or (not ys.isdigit()):
            return
        x,y = int(xs),int(ys)
        #print 'type %s %s'%(x,y)
        self.setCurrentPos(x,y)
        
    def button1(self,event):
        x,y = event.x,event.y
        #print 'click %s %s'%(x,y)
        x -= self.padx
        y -= self.pady
        self.setCurrentPos(x,y)
    def button1motion(self,event):
        x,y = event.x,event.y
        #print 'drag %s %s'%(x,y)
        x -= self.padx
        y -= self.pady
        self.setCurrentPos(x,y)

    def setCurrentPos(self,x,y):
        w,h = self.frames[self.curFrame].getSize()
        if x>=w or y>=h or x<0 or y<0:
            return
        self.pos = (x,y)
        self.xvar.set(str(x))
        self.yvar.set(str(y))
        r,g,b = color = self.frames[self.curFrame][x,y]
        self.colorFrame['bg'] = self.rgbToHtml(color)
        self.rvar.set(str(r))
        self.gvar.set(str(g))
        self.bvar.set(str(b))

        # reset cursor
        cs = self.cursorSize
        cw = self.cursorWindow
        x,y = x+self.padx,y+self.pady
        self.canvas.coords(self.cursorTags[0],x-cs,y-cs,x-cw,y-cw)
        self.canvas.coords(self.cursorTags[1],x-cs,y+cs,x-cw,y+cw)
        self.canvas.coords(self.cursorTags[2],x+cs,y-cs,x+cw,y-cw)
        self.canvas.coords(self.cursorTags[3],x+cs,y+cs,x+cw,y+cw)
        gray = 255 - (r+g+b)/3.
        if gray>80:
            gray = 255
        inverse = self.rgbToHtml((gray,gray,gray))
        for ct in self.cursorTags:
            self.canvas.itemconfigure(ct,fill=inverse)
        
    def fentryReturn(self,event):
        fs = self.fvar.get()
        if not fs.isdigit():
            return
        fnum = int(fs)
        if fnum >= len(self.frames):
            fnum = len(self.frames)-1
        self.setFrame(fnum)
    def whichFrame(self,x):
        width = self.frameSelector.winfo_width()
        fwidth = float(width)/len(self.frames)
        return int(x/fwidth)
    def fsButton1(self,event):
        x,y = event.x,event.y
        frame = self.whichFrame(x)
        if frame != self.curFrame:
            self.setFrame(frame)
        #print 'click %s %s'%(x,y)
    def fsButton1Motion(self,event):
        x,y = event.x,event.y
        frame = self.whichFrame(x)
        if frame != self.curFrame:
            self.setFrame(frame)
        #print 'drag %s %s'%(x,y)
    def setFrame(self,fnumber):
        if fnumber >= len(self.frames):
            return
        self.fvar.set(fnumber)
        self.curFrame = fnumber
        self.refreshPicture()
        self.setCurrentPos(*self.pos)

    def escape(self,event):
        self.destroy()

        


#######################################
#                                     #
#      Sound class and Functions      #
#      Sample Class and Functions     #
#                                     #
#######################################

sound_functions = [
    'blockingPlay',
    'duplicateSound',
    'getDuration',
    'getLength',
    'getNumSamples',
    'getSampleObjectAt',
    'getSamples',
    'getSampleValue',
    'getSampleValueAt',
    'getSamplingRate',
    'getSound',
    'makeEmptySound',
    'makeEmptySoundBySeconds',
    'makeSound',
    'play',
    'playNote',
    'setSampleValue',
    'setSampleValueAt',
    'stopPlaying',
    'writeSoundTo',
    'openSoundTool',
    ]

def blockingPlay(sound):
    '''
    Plays the sound provided and makes sure no other sound plays at
    the exact same time.
    '''
    sound.blockingPlay()

def duplicateSound(sound):
    '''
    Takes a sound and returns a new sound object with the same sample
    values as the original.
    '''
    sound.duplicateSound()

def getDuration(sound):
    '''
    Takes a sound and returns the number of seconds that sound lasts.
    '''
    return sound.getDuration()

def getLength(sound):
    '''
    Takes a sound and returns the number of samples in that sound.
    '''
    return sound.getLength()

def getNumSamples(sound):
    '''
    Takes a sound and returns the number of samples in that sound.
    '''
    return sound.getNumSamples()

def getSampleObjectAt(sound, index):
    '''
    Takes a sound and an index and returns the sample object at that
    index.
    '''
    return sound.getSampleObjectAt(index)

def getSamples(sound):
    '''
    Takes a sound and returns a list ofSamples in that sound.
    '''
    return sound.getSamples()

def getSampleValue(sample):
    '''
    Takes a sample object and returns its value (between -32768 and
    32767).
    '''
    return sample.getSampleValue()

def getSampleValueAt(sound, index):
    '''
    Takes a sound and an index, and returns the value of the sample at
    the given index in the sound.
    '''
    return sound.getSampleValueAt(index)

def getSamplingRate(sound):
    '''
    Takes a sound and returns the number representing the number of
    samples in each second for the sound.
    '''
    return sound.getSamplingRate()

def getSound(sample):
    '''
    Takes a sample object and returns the sound that it belongs to.
    '''
    return sample.getSound()

def makeEmptySound(numSamples, samplingRate = 22050):
    '''
    Takes one or two integers. Returns an empty sound object with the
    given number of samples and optionally the given sampling
    rate. Default rate is 22050 bits/second. The resulting sound must
    not be longer than 600 seconds. Prints an error statement if
    numSamples or sampling Rate are less than 0, or if
    (numSamples/samplingRate) > 600.
    '''
    pass

def makeEmptySoundBySeconds(duration, samplingRate = 22050):
    '''
    Takes a floating point duration and optionally an integer
    samplingRate (default 22050). If the given arguments do not
    multiply to an integer, the number of samples is rounded
    up. Prints an error statement if duration or samplingRate are less
    than 0, or if duration is greater than 600.
    '''
    pass

def makeSound(path):
    '''
    Takes a filename, reads the file, and creates a sound object from
    it. Returns the sound object.
    '''
    s = Sound()
    s.fromFile(path)
    return s

def play(sound):
    '''
    Plays a sound object. Will play sounds ontop of each other. Use
    blockingPlay(sound) to avoid this.  No return value.
    '''
    sound.play()

def playNote(note, duration, intensity=64):
    '''
    Plays the given MIDI note. No return value.  Default intensity is
    64.
    '''
    pass

def setSampleValue(sample, value):
    '''
    Takes a sample object and a value (should be between -32768 and
    32767), and sets the sample to that value.
    '''
    sample.setSampleValue(value)

def setSampleValueAt(sound, index, value):
    '''
    Takes a sound, index, and value (between -32768 and 32767) and
    sets the value of the sample at the index of the sound to the
    given value.
    '''
    sound.setSampleValueAt(index, value)

def stopPlaying(sound):
    '''
    Stops a sound that is currently playing.
    '''
    sound.stopPlaying()

def writeSoundTo(sound, path):
    '''
    Takes a sound and filename and write the sound to that file as a
    WAV file.
    '''
    sound.writeSoundTo(path)

def openSoundTool(sound):
    '''
    Opens the sound tool explorer, which lets you examine the waveform
    of a sound.
    '''
    sound.openSoundTool



import wave,struct

class Sound:
    def __init__(self):
        self.path = None
        self.samplingRate = 0
        self.samples = []

    def __len__(self):
        return len(self.samples)
    def __getitem__(self,index):
        return self.samples[index]
    def __setitem__(self,index,value):
        self.samples[index] = value

    def init(self):
        pass

    def fromFile(self,filename):
        mpath = getPath(filename)
        wr = wave.open(mpath)
        self.samplingRate = wr.getframerate()
        nFrames = wr.getnframes()
        sampWidth = wr.getsampwidth()
        frameData = wr.readframes(nFrames)
        if sampWidth == 2:
            structFormat = 'h'
        elif sampWidth == 4:
            structFormat = 'i'
        else:
            raise NotImplementedError, 'Can\'t handle this WAV format'
        for i in range(0,len(frameData),sampWidth):
            samp = struct.unpack(structFormat,frameData[i:i+sampWidth])
            self.samples.append(samp[0])
        
        
    def blockingPlay(self):
        '''
        Plays the sound provided and makes sure no other sound plays
        at the exact same time.
        '''
        pass

    def duplicateSound(self):
        '''
        Takes a sound and returns a new sound object with the same
        sample values as the original.
        '''
        s=Sound()
        s.path = self.path
        s.samplingRate = self.samplingRate
        s.samples = list(self.samples)

    def getDuration(self):
        '''
        Takes a sound and returns the number of seconds that sound
        lasts.
        '''
        return float(len(self))/self.samplingRate

    def getLength(self):
        '''
        Takes a sound and returns the number of samples in that sound.
        '''
        return len(self)

    def getNumSamples(self):
        '''
        Takes a sound and returns the number of samples in that sound.
        '''
        return len(self)

    def getSampleObjectAt(self, index):
        '''
        Takes a sound and an index and returns the sample object at
        that index.
        '''
        return Sample(self,index)

    def getSamples(self):
        '''
        Takes a sound and returns a list ofSamples in that sound.
        '''
        def samples(self=self):
            for i in xrange(len(self)):
                yield Sample(self,i)
        return samples()

    def getSampleValueAt(self, index):
        '''
        Takes a sound and an index, and returns the value of the
        sample at the given index in the sound.
        '''
        return self.samples[index]

    def getSamplingRate(self):
        '''
        Takes a sound and returns the number representing the number
        of samples in each second for the sound.
        '''
        pass
    
    def play(self):
        '''
        Plays a sound object. Will play sounds ontop of each
        other. Use blockingPlay(sound) to avoid this.  No return
        value.
        '''
        pass

    def setSampleValueAt(self, index, value):
        '''
        Takes a sound, index, and value (between -32768 and 32767) and
        sets the value of the sample at the index of the sound to the
        given value.
        '''
        pass

    def stopPlaying(self):
        '''
        Stops a sound that is currently playing.
        '''
        pass

    def writeSoundTo(self, path):
        '''
        Takes a sound and filename and write the sound to that file as
        a WAV file.
        '''
        pass

    def openSoundTool(self):
        '''
        Opens the sound tool explorer, which lets you examine the
        waveform of a sound.
        '''
        pass


class Sample:

    def __init__(self,sound,index):
        self.sound = sound
        self.index = index

    def getSampleValue(self):
        '''
        Takes a sample object and returns its value (between -32768
        and 32767).
        '''
        return self.sound.samples[self.index]

    def getSound(self):
        '''
        Takes a sample object and returns the sound that it belongs
        to.
        '''
        return self.sound

    def setSampleValue(self, value):
        '''
        Takes a sample object and a value (should be between -32768
        and 32767), and sets the sample to that value.
        '''
        self.sound.samples[self.index] = value



